package com.fdd.spring.ioc;


import com.fdd.spring.annotation.AutoWired;
import com.fdd.spring.annotation.Component;
import com.fdd.spring.annotation.ComponentScan;
import com.fdd.spring.annotation.Scope;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.concurrent.ConcurrentHashMap;


//自己实现一个Spring的IOC容器
//1. 实现扫描指定包下的所有类
//2. 实现将扫描到的类，根据注解的方式，放到IOC容器中
//3. 实现从IOC容器中获取bean对象
//4. 实现依赖注入
//5. 实现AOP
public class FddSpringApplicationContext {
    private Class configClass;
    //通过反射创建的对象，基于注解的方式
    private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>();

    //创建一个beanDefinitionMap，用于存储bean的定义信息
    private final ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();

    public FddSpringApplicationContext(Class configClass) {
        //扫描指定包下的所有类，得到类的名字和bean的定义信息，放入beanDefinitionMap中
        beanDefinitionsByScan(configClass);
        System.out.println("beanDefinitionMap:" + beanDefinitionMap);

        //通过beanDefinitionMap中的bean定义信息，创建bean对象，放入singletonObjects中
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                Object bean = createBean(beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }


    public void beanDefinitionsByScan(Class configClass) {
        this.configClass = configClass;
        //解析配置类,得到它的注解和值
        Annotation annotation = this.configClass.getAnnotation(ComponentScan.class);
        if (annotation == null) {
            throw new RuntimeException("配置类缺少@ComponentScan注解");
        }
        //获取注解的值,也就是要扫描的包的路径
        String path = ((ComponentScan) annotation).value();
        //扫描path路径下的所有类，得到类的名字。注意要将包的路径转换成文件的路径

        URL resource = this.getClass().getClassLoader().getResource(path.replace(".", "/"));
        System.out.println(resource);
        //获取下面的类名
        assert resource != null;
        File file = new File(resource.getPath());
        for (File f : file.listFiles()) {

            if (f.getPath().endsWith(".class")) {
                //得到全类名
                String className = path + "." + f.getName().replace(".class", "");
                System.out.println("className:" + className);
                //反射创建对象
                try {
                    Class<?> aClass = Class.forName(className);
                    //判断是否是组件类
                    if (aClass.isAnnotationPresent(Component.class)) {
                        //是组件类，就放到ioc容器中
                        String beanName = aClass.getDeclaredAnnotation(Component.class).value();
                        if ("".equals(beanName)) {//没有指定beanName，就使用类名的首字母小写作为beanName
                            beanName = StringUtils.uncapitalize(aClass.getSimpleName());
                        }
                        //获取scope属性
                        String scope = "singleton";
                        if (aClass.isAnnotationPresent(Scope.class)) {
                            scope = aClass.getDeclaredAnnotation(Scope.class).value();
                            if (!"prototype".equalsIgnoreCase(scope)) {
                                scope = "singleton";
                            }
                        }
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setBeanClass(aClass);
                        beanDefinition.setScope(scope);

                        beanDefinitionMap.put(beanName, beanDefinition);

                    } else {
                        System.out.println("不是组件类：" + aClass.getName());
                    }
                } catch (Exception e) {
                    throw new RuntimeException();
                }
            }
        }
    }

    //createBean方法，用于创建bean对象
    public Object createBean(BeanDefinition beanDefinition) {
        Class<?> beanClass = beanDefinition.getBeanClass();
        try {
            Object instance = beanClass.newInstance();
            //依赖注入
            for(Field field : beanClass.getDeclaredFields()){
                //判断是否有AutoWired注解
                if(field.isAnnotationPresent(AutoWired.class)){
                    //有AutoWired注解，就进行依赖注入
                    //处理required属性
                    boolean required = field.getDeclaredAnnotation(AutoWired.class).required();

                    //获取AutoWired注解的字段名
                    String beanName = field.getName();

                    //根据字段名获取bean对象
                    Object bean = null;
                    try {
                        bean = getBean(beanName);
                    } catch (Exception ignored) {
                        if(required){
                            throw new RuntimeException("没有找到beanName为："+beanName+"的bean,无法注入");
                        }
                    }

                    if(bean!=null){
                        //设置属性的值,私有属性需要暴破
                        field.setAccessible(true);
                        field.set(instance,bean);
                    }

                }
            }
            return instance;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

    }


    //getBean方法，用于从IOC容器中获取bean对象。单例对象从singletonObjects中获取，原型对象每次都要创建一个新的对象
    public Object getBean(String beanName) {
        //传入的beanName在singletonObjects中是否存在
        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
                return singletonObjects.get(beanName);
            } else if ("prototype".equalsIgnoreCase(beanDefinition.getScope())) {
                //原型模式，每次获取都要创建一个新的对象
                return createBean(beanDefinition);
            }
        } else {
            throw new RuntimeException("没有找到beanName为：" + beanName + "的bean");
        }
        return null;
    }
}
